﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lidgren.Network;
using System.Reflection;

namespace Kaleta.Network
{
    /// <summary>
    /// This is abstract class, which instances of are distributed over network. 
    /// In a network session there are unique objects of type DistributableObject.
    /// </summary>
    public abstract class DistributableObject
    {
        /// <summary>
        /// True if this object is already in server or created by server. 
        /// Otherwise false.
        /// </summary>
        public bool IsCreated { get; set; }
        public int Key { get; set; }

        /// <summary>
        /// This is internal property.
        /// </summary>
        internal bool Distribute { get; set; }

        public DistributableObject()
        {
            this.Distribute = true;
        }

        
    }

    /// <summary>
    /// Represets a property, which value is dependent on server update.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DistributableProperty<T> where T : struct
    {
        private T value;
        private DistributableObject owner;
        private PropertyInfo propertyInfo;
        private NetChannel channel;

        private DistributableProperty(DistributableObject owner, PropertyInfo propInfo, NetChannel channel)
        {
            this.owner = owner;
            this.propertyInfo = propInfo;
            this.channel = channel;
        }


        /// <summary>
        /// If client (this class) sets a new value for this property, the client requests the server for update of this value.
        /// After invoking of this method, the client will still use old value until it gets from server new updated value.
        /// </summary>
        /// <param name="newValue"></param>
        public void SetValue(T newValue)
        {
            //set from me:
            if (this.owner.Distribute)
            {
                DistributionSerializer.Serialize(DistributionSession.clientSendBuffer[(int) this.channel],
                            DistributionType.Update, this.owner, this.propertyInfo, newValue, this.channel);
                
            }
            //set from update:
            else
                this.value = newValue;
        }

        /// <summary>
        /// Gets value for this property. If server was not yet updated, old value will be returned. 
        /// If server has been updated (and notified the client), new value will be returned.
        /// </summary>
        /// <returns>New or old value depending on server notification(update)</returns>
        public T GetValue()
        {
            return this.value;
        }

        /// <summary>
        /// Registers property for network distribution.
        /// </summary>
        /// <param name="propertyName">Name of property to register</param>
        /// <param name="typeOfClass">Type of property to register. Can only be of type struct. No reference objects supported.</param>
        /// <returns></returns>
        static public DistributableProperty<T> Register(string propertyName, DistributableObject owner, NetChannel channel)
        {
            PropertyInfo propertyInfo = owner.GetType().GetProperties().Single(property => property.Name.Equals(propertyName));

            return new DistributableProperty<T>(owner, propertyInfo, channel);
        }


        /// <summary>
        /// Registers property for network distribution.
        /// Channel is unreliable.
        /// </summary>
        /// <param name="propertyName">Name of property to register</param>
        /// <param name="typeOfClass">Type of property to register. Can only be of type struct. No reference objects supported.</param>
        /// <returns></returns>
        static public DistributableProperty<T> Register(string propertyName, DistributableObject owner)
        {
            PropertyInfo propertyInfo = owner.GetType().GetProperties().Single(property => property.Name.Equals(propertyName));

            return new DistributableProperty<T>(owner, propertyInfo, NetChannel.Unreliable);
        }
    }



    public class DistributableObjectException : Exception
    {
        public DistributableObjectException(string message) : base(message) { }

    }
}
